

#include "BackupFSFileNodes.h"
#include "logger/BackupFSLogger.h"
#include "BackupFSCore.h"
#include "BackupFSSql.h"
namespace BackupFS
{

	BackupFSFileNode::BackupFSFileNode(const std::wstring& filename, bool is_directory,
		DWORD file_attr,
		const PDOKAN_IO_SECURITY_CONTEXT security_context)
		:IsDirectory(is_directory), Attributes(file_attr), _FileName(filename)
	{
		// No lock needed, FileNode is still not in a directory
		Logger->Debug("create a new node,", W2S(filename), " directory?", is_directory);
		Times.Reset();

		if (security_context && security_context->AccessState.SecurityDescriptor)
		{
			Security.SetDescriptor(security_context->AccessState.SecurityDescriptor);
		}
	}
	BackupFSFileNode::~BackupFSFileNode()
	{
		_DataBlocks.clear();
		_Streams.clear();
		_LockedRegions.clear();
	}
	void BackupFSFileNode::Close(ULONG process_id)
	{
		if (_LockedRegions.size() == 0)
		{
			return ;
		}
		/*
		Handle the lock region case
		*/
		std::lock_guard<std::mutex> locker(_DataMutex);
		auto it = _LockedRegions.begin();
		while (it != _LockedRegions.end())
		{
			if (it->get()->ProcessID != process_id)
			{
				it++;
				continue;
			}
			it = _LockedRegions.erase(it);
			if (it != _LockedRegions.end())
			{
				it++;
			}
		}
	}
	void BackupFSFileNode::Cleanup()
	{

	}
	/*
	The read algorithm is similar with _Update()
	The only difference is read is limited the file size
	can't read data beyond of the file size
	*/
	DWORD BackupFSFileNode::Read(LPVOID buffer, LONGLONG bufferlength, LONGLONG offset)
	{
		std::lock_guard<std::mutex> locker(_DataMutex);

		if (_DataBlocks.empty() || offset >= FileSize)
		{
			return 0;
		}
		LONGLONG read_size = 0;
		LONGLONG file_offset = offset;
		LONGLONG left_to_read_bytes = bufferlength;
		// if the requested read length exceed the file size,
		// limit the read size
		if (offset + bufferlength >= FileSize)
		{
			left_to_read_bytes = FileSize - offset;
		}
		for (auto& block : _DataBlocks)
		{
			//if offset is in this block
			if (_OffsetInBlock(block, file_offset))
			{
				//if the block can contain the write request
				//valid length 
				LONGLONG valid_length = block->Length - (file_offset - block->OffsetInFile);
				LONGLONG block_offset = file_offset - block->OffsetInFile;
				LONGLONG temp_read_size = 0;
				if (left_to_read_bytes <= valid_length)
				{
					valid_length = left_to_read_bytes;
				}

				temp_read_size = FS_CORE->Read((char*)buffer + read_size, valid_length, block->OffsetInData + block_offset);
				
				read_size += temp_read_size;
				left_to_read_bytes -= temp_read_size;
				file_offset += temp_read_size;

			}
			if (read_size >= bufferlength or file_offset >= FileSize)
			{
				break;
			}
		}
		return (DWORD)read_size;
	}
	/*
	* three cases:
	1.append
	append the data to the end of the file
	2.update
	update the datais
	3.update and append
	update some and append some data
	*/
	DWORD BackupFSFileNode::Write(LPCVOID buffer, LONGLONG number_of_bytes_to_write, LONGLONG offset)
	{
		if (!number_of_bytes_to_write)
		{
			return 0;
		}
		std::lock_guard<std::mutex> locker(_DataMutex);
		LONGLONG new_file_size = 0;
		LONGLONG written_size = 0;
		if (_DataBlocks.empty())
		{
			// offset maybe will exceed the file size
			// 
			LONGLONG need_length = offset + number_of_bytes_to_write;
			auto block = FS_CORE->GetNewDataBlock(offset, need_length);
			block->OffsetInFile = 0;
			Logger->Debug("Block info:id=", block->Id, ",OffsetInFile=",block->OffsetInFile,",OffsetInData=", block->OffsetInData, ",Length=",block->Length);
			_DataBlocks.push_back(block);
			LONGLONG block_offset = offset % BACKUPFS_PAGE_SIZE;
			written_size = FS_CORE->Write(buffer, number_of_bytes_to_write, block->OffsetInData + block_offset);
			_AllocatedSize = block->Length;
			BackupFSSql::AddDataBlock(BackupFSCore::GetInstance()->GetDb(), block, NodeId);
		}
		else
		{
			//exceed the last block,append data
			if (offset >= _AllocatedSize)
			{
				written_size = _Append(buffer, number_of_bytes_to_write, offset);

			}
			else if (offset + (LONGLONG)number_of_bytes_to_write <= _AllocatedSize)
			{
				//update the file
				written_size = _Update(buffer, number_of_bytes_to_write, offset);
			}
			else
			{
				//update and append
				LONGLONG exceed_size = (offset + (LONGLONG)number_of_bytes_to_write) - _AllocatedSize;
				LONGLONG need_update_size = number_of_bytes_to_write - exceed_size;
				written_size += (LONGLONG)_Update(buffer, need_update_size, offset);
				written_size += _Append((char*)buffer + need_update_size, exceed_size, _AllocatedSize);
			}
		}
		if (offset + number_of_bytes_to_write > FileSize)
		{
			LONGLONG used_size = offset + number_of_bytes_to_write - FileSize; ;
			FileSize = offset + number_of_bytes_to_write;
			BackupFSSql::UpdateFileSize(BackupFSCore::GetInstance()->GetDb(), NodeId, FileSize);
		}
		LONGLONG curr_time = FileTimes::GetCurrentTime();
		SetFileTime(0, curr_time, curr_time);
		return (DWORD)written_size;
	}
	DWORD BackupFSFileNode::_Append(LPCVOID buffer, LONGLONG number_of_bytes_to_write, LONGLONG offset)
	{
		LONGLONG written_size = 0;
		auto& last_block = _DataBlocks.back();
		LONGLONG added_size = offset - last_block->OffsetInFile - last_block->Length;
		auto block = FS_CORE->GetNewDataBlock(offset, number_of_bytes_to_write + added_size);
		Logger->Debug("Block info:id=", block->Id, ",OffsetInFile=", block->OffsetInFile, ",OffsetInData=", block->OffsetInData, ",Length=", block->Length);

		// adjust the OffsetInFile as offset exceeds added_size
		// as the offset will exceed the file size
		// 
		LONGLONG adjust_added_size = block->OffsetInFile - last_block->OffsetInFile - last_block->Length;
		block->OffsetInFile -= adjust_added_size;
		LONGLONG block_offset = adjust_added_size + offset % BACKUPFS_PAGE_SIZE;
		written_size = FS_CORE->Write(buffer, number_of_bytes_to_write, block->OffsetInData + block_offset);
		_AllocatedSize = block->OffsetInFile + block->Length;
		if(adjust_added_size !=0)
			Logger->Debug("Adjust Block info:id=", block->Id, ",OffsetInFile=", block->OffsetInFile, ",OffsetInData=", block->OffsetInData, ",Length=", block->Length);

		//can merge?
		if (block->OffsetInData == last_block->OffsetInData + last_block->Length)
		{
			last_block->Length = (block->OffsetInFile - last_block->OffsetInFile) + block->Length;

			// update the block
			BackupFSSql::UpdateDataBlock(BackupFSCore::GetInstance()->GetDb(), last_block);
		}
		else
		{
			_DataBlocks.push_back(block);
			// add the block to db
			BackupFSSql::AddDataBlock(BackupFSCore::GetInstance()->GetDb(), block, NodeId);
		}
		return (DWORD)written_size;
	}
	bool BackupFSFileNode::_OffsetInBlock(const std::shared_ptr<DATA_BLOCK>& block, LONGLONG offset)
	{
		if (block->OffsetInFile + block->Length > offset and block->OffsetInFile <= offset)
		{
			return true;
		}
		return false;
	}
	/*
	Update the exist block
	*/
	DWORD BackupFSFileNode::_Update(LPCVOID buffer, LONGLONG number_of_bytes_to_write, LONGLONG offset)
	{
		LONGLONG written_size = 0;
		LONGLONG file_offset = offset;
		LONGLONG left_to_write_bytes = number_of_bytes_to_write;
		for (auto& block : _DataBlocks)
		{
			//if offset is in this block
			if (_OffsetInBlock(block,file_offset))
			{
				//if the block can contain the write request
				//valid length 
				LONGLONG valid_length = block->Length - (file_offset - block->OffsetInFile);
				LONGLONG block_offset = file_offset - block->OffsetInFile;
				if (left_to_write_bytes <= valid_length)
				{
					written_size += FS_CORE->Write((char*)buffer + written_size, left_to_write_bytes, block->OffsetInData + block_offset);
				}
				else
				{
					written_size += FS_CORE->Write((char*)buffer + written_size, valid_length, block->OffsetInData + block_offset);
				}

				left_to_write_bytes = number_of_bytes_to_write - written_size;
				file_offset = offset + written_size;

			}
			if (written_size >= number_of_bytes_to_write)
			{
				break;
			}
		}
		return (DWORD)written_size;
	}

	const LONGLONG BackupFSFileNode::GetFileSize()
	{
		std::lock_guard<std::mutex> locker(_DataMutex);
		return FileSize;
	}

	void BackupFSFileNode::SetEndOfFile(const LONGLONG& byte_offset)
	{
		Logger->Debug(W2S(_FileName), " set endoffile,offset is ", byte_offset);
		std::lock_guard<std::mutex> locker(_DataMutex);
		if (FileSize < byte_offset)
		{
			auto block = FS_CORE->GetNewDataBlock(FileSize,byte_offset - FileSize);
			Logger->Debug("Block info:id=", block->Id, ",OffsetInFile=", block->OffsetInFile, ",OffsetInData=", block->OffsetInData, ",Length=", block->Length);

			_DataBlocks.push_back(block);
			_AllocatedSize = ROUND_UP_PAGES(byte_offset) * BACKUPFS_PAGE_SIZE;
			BackupFSSql::AddDataBlock(BackupFSCore::GetInstance()->GetDb(), block, NodeId);
		}
		FileSize = byte_offset;
		BackupFSSql::UpdateFileSize(BackupFSCore::GetInstance()->GetDb(), NodeId, FileSize);
	}

	const std::wstring BackupFSFileNode::GetFileName()
	{
		std::shared_lock lock(_FileNameMutex);
		return _FileName;
	}

	void BackupFSFileNode::SetFileName(const std::wstring& f)
	{
		std::unique_lock lock(_FileNameMutex);
		_FileName = f;
	}
	bool BackupFSFileNode::SetSecurity(PSECURITY_DESCRIPTOR security_descriptor)
	{
		Security.SetDescriptor(security_descriptor);
		return BackupFSSql::UpdateFileSecurity(BackupFSCore::GetInstance()->GetDb(),NodeId, this->Security.Descriptor.get(),this->Security.DescriptorSize);
	}
	bool BackupFSFileNode::IsLockedOffset(LONGLONG offset, ULONG process_id)
	{
		if (_LockedRegions.size() == 0)
		{
			return false;
		}
		std::lock_guard<std::mutex> locker(_DataMutex);
		for (auto &region : _LockedRegions)
		{
			if (region->ProcessID == process_id)
			{
				continue;
			}
			if (region->Offset <= offset && (region->Offset + region->Length) > offset)
			{
				return true;
			}
		}
		return false;
	}
	bool BackupFSFileNode::LockRegion(LONGLONG offset, LONGLONG length, ULONG process_id)
	{
		std::shared_ptr<LOCK_REGION> region = std::make_shared<LOCK_REGION>();
		region->Offset = offset;
		region->Length = length;
		region->ProcessID = process_id;
		_LockedRegions.push_back(region);
		return true;
	}
	bool BackupFSFileNode::UnLockRegion(LONGLONG offset, LONGLONG length, ULONG process_id)
	{
		if (_LockedRegions.size() == 0)
		{
			return true;
		}
		std::lock_guard<std::mutex> locker(_DataMutex);
		auto it = _LockedRegions.begin();
		while(it != _LockedRegions.end())
		{
			if (it->get()->ProcessID != process_id)
			{
				it++;
				continue;
			}
			// simplize the logic,ingore the length
			if (it->get()->Offset <= offset && offset <= (it->get()->Offset+ it->get()->Length))
			{
				_LockedRegions.erase(it);
				break;
			}
			it++;
		}
		return true;
	}
	
	bool BackupFSFileNode::SetFileTime(LONGLONG create_time, LONGLONG write_time, LONGLONG access_time)
	{
		if (create_time > 0)
		{
			Times.Creation = create_time;
		}
		if (write_time > 0)
		{
			Times.LastWrite = write_time;
		}
		if (access_time > 0)
		{
			Times.LastAccess = access_time;
		}
		if (create_time > 0 or write_time > 0 or access_time > 0)
		{
			return BackupFSSql::UpdateFileTime(BackupFSCore::GetInstance()->GetDb(), NodeId, Times.Creation, Times.LastWrite, Times.LastAccess);
		}
		return true;
	}

	void BackupFSFileNode::AddStream(const std::shared_ptr<BackupFSFileNode>& stream)
	{
		std::lock_guard<std::mutex> locker(_DataMutex);

		_Streams[stream->GetFileName()] = stream;
	}

	void BackupFSFileNode::RemoveStream(const std::shared_ptr<BackupFSFileNode>& stream)
	{
		std::lock_guard<std::mutex> locker(_DataMutex);

		_Streams.erase(stream->GetFileName());
	}

	std::unordered_map<std::wstring, std::shared_ptr<BackupFSFileNode> >BackupFSFileNode::GetStreams() 
	{
		std::lock_guard<std::mutex> locker(_DataMutex);

		return _Streams;
	}
}  // namespace BackupFS
